//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#pragma once

#include <algorithm>  // all_of
#include <cctype>     // isdigit
#include <cerrno>     // errno, ERANGE
#include <cstdlib>    // strtoull
#ifndef JSON_NO_IO
#include <iosfwd>  // ostream
#endif             // JSON_NO_IO
#include <limits>  // max
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/string_escape.hpp>
#include <nlohmann/detail/value_t.hpp>
#include <numeric>  // accumulate
#include <string>   // string
#include <utility>  // move
#include <vector>   // vector

NLOHMANN_JSON_NAMESPACE_BEGIN

/// @brief JSON Pointer defines a string syntax for identifying a specific value
/// within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template <typename RefStringType>
class json_pointer {
  // allow basic_json to access private members
  NLOHMANN_BASIC_JSON_TPL_DECLARATION
  friend class basic_json;

  template <typename>
  friend class json_pointer;

  template <typename T>
  struct string_t_helper {
    using type = T;
  };

  NLOHMANN_BASIC_JSON_TPL_DECLARATION
  struct string_t_helper<NLOHMANN_BASIC_JSON_TPL> {
    using type = StringType;
  };

 public:
  // for backwards compatibility accept BasicJsonType
  using string_t = typename string_t_helper<RefStringType>::type;

  /// @brief create JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/
  explicit json_pointer(const string_t &s = "") : reference_tokens(split(s)) {}

  /// @brief return a string representation of the JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/to_string/
  string_t to_string() const {
    return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
                           string_t{},
                           [](const string_t &a, const string_t &b) {
                             return detail::concat(a, '/', detail::escape(b));
                           });
  }

  /// @brief return a string representation of the JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/
  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
  operator string_t() const { return to_string(); }

#ifndef JSON_NO_IO
  /// @brief write string representation of the JSON pointer to stream
  /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/
  friend std::ostream &operator<<(std::ostream &o, const json_pointer &ptr) {
    o << ptr.to_string();
    return o;
  }
#endif

  /// @brief append another JSON pointer at the end of this JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
  json_pointer &operator/=(const json_pointer &ptr) {
    reference_tokens.insert(reference_tokens.end(),
                            ptr.reference_tokens.begin(),
                            ptr.reference_tokens.end());
    return *this;
  }

  /// @brief append an unescaped reference token at the end of this JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
  json_pointer &operator/=(string_t token) {
    push_back(std::move(token));
    return *this;
  }

  /// @brief append an array index at the end of this JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
  json_pointer &operator/=(std::size_t array_idx) {
    return *this /= std::to_string(array_idx);
  }

  /// @brief create a new JSON pointer by appending the right JSON pointer at
  /// the end of the left JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
  friend json_pointer operator/(const json_pointer &lhs,
                                const json_pointer &rhs) {
    return json_pointer(lhs) /= rhs;
  }

  /// @brief create a new JSON pointer by appending the unescaped token at the
  /// end of the JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
  friend json_pointer operator/(
      const json_pointer &lhs,
      string_t token)  // NOLINT(performance-unnecessary-value-param)
  {
    return json_pointer(lhs) /= std::move(token);
  }

  /// @brief create a new JSON pointer by appending the array-index-token at the
  /// end of the JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
  friend json_pointer operator/(const json_pointer &lhs,
                                std::size_t array_idx) {
    return json_pointer(lhs) /= array_idx;
  }

  /// @brief returns the parent of this JSON pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/
  json_pointer parent_pointer() const {
    if (empty()) {
      return *this;
    }

    json_pointer res = *this;
    res.pop_back();
    return res;
  }

  /// @brief remove last reference token
  /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/
  void pop_back() {
    if (JSON_HEDLEY_UNLIKELY(empty())) {
      JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent",
                                              nullptr));
    }

    reference_tokens.pop_back();
  }

  /// @brief return last reference token
  /// @sa https://json.nlohmann.me/api/json_pointer/back/
  const string_t &back() const {
    if (JSON_HEDLEY_UNLIKELY(empty())) {
      JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent",
                                              nullptr));
    }

    return reference_tokens.back();
  }

  /// @brief append an unescaped token at the end of the reference pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/push_back/
  void push_back(const string_t &token) { reference_tokens.push_back(token); }

  /// @brief append an unescaped token at the end of the reference pointer
  /// @sa https://json.nlohmann.me/api/json_pointer/push_back/
  void push_back(string_t &&token) {
    reference_tokens.push_back(std::move(token));
  }

  /// @brief return whether pointer points to the root document
  /// @sa https://json.nlohmann.me/api/json_pointer/empty/
  bool empty() const noexcept { return reference_tokens.empty(); }

 private:
  /*!
  @param[in] s  reference token to be converted into an array index

  @return integer representation of @a s

  @throw parse_error.106  if an array index begins with '0'
  @throw parse_error.109  if an array index begins not with a digit
  @throw out_of_range.404 if string @a s could not be converted to an integer
  @throw out_of_range.410 if an array index exceeds size_type
  */
  template <typename BasicJsonType>
  static typename BasicJsonType::size_type array_index(const string_t &s) {
    using size_type = typename BasicJsonType::size_type;

    // error condition (cf. RFC 6901, Sect. 4)
    if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) {
      JSON_THROW(detail::parse_error::create(
          106, 0,
          detail::concat("array index '", s, "' must not begin with '0'"),
          nullptr));
    }

    // error condition (cf. RFC 6901, Sect. 4)
    if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) {
      JSON_THROW(detail::parse_error::create(
          109, 0, detail::concat("array index '", s, "' is not a number"),
          nullptr));
    }

    const char *p = s.c_str();
    char *p_end = nullptr;
    errno = 0;  // strtoull doesn't reset errno
    const unsigned long long res =
        std::strtoull(p, &p_end, 10);  // NOLINT(runtime/int)
    if (p == p_end                     // invalid input or empty string
        || errno == ERANGE             // out of range
        || JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) !=
                                s.size()))  // incomplete read
    {
      JSON_THROW(detail::out_of_range::create(
          404, detail::concat("unresolved reference token '", s, "'"),
          nullptr));
    }

    // only triggered on special platforms (like 32bit), see also
    // https://github.com/nlohmann/json/pull/2203
    if (res >=
        static_cast<unsigned long long>(
            (std::numeric_limits<size_type>::max)()))  // NOLINT(runtime/int)
    {
      JSON_THROW(detail::out_of_range::create(
          410, detail::concat("array index ", s, " exceeds size_type"),
          nullptr));  // LCOV_EXCL_LINE
    }

    return static_cast<size_type>(res);
  }

  JSON_PRIVATE_UNLESS_TESTED : json_pointer top() const {
    if (JSON_HEDLEY_UNLIKELY(empty())) {
      JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent",
                                              nullptr));
    }

    json_pointer result = *this;
    result.reference_tokens = {reference_tokens[0]};
    return result;
  }

 private:
  /*!
  @brief create and return a reference to the pointed to value

  @complexity Linear in the number of reference tokens.

  @throw parse_error.109 if array index is not a number
  @throw type_error.313 if value cannot be unflattened
  */
  template <typename BasicJsonType>
  BasicJsonType &get_and_create(BasicJsonType &j) const {
    auto *result = &j;

    // in case no reference tokens exist, return a reference to the JSON value
    // j which will be overwritten by a primitive value
    for (const auto &reference_token : reference_tokens) {
      switch (result->type()) {
      case detail::value_t::null: {
        if (reference_token == "0") {
          // start a new array if reference token is 0
          result = &result->operator[](0);
        } else {
          // start a new object otherwise
          result = &result->operator[](reference_token);
        }
        break;
      }

      case detail::value_t::object: {
        // create an entry in the object
        result = &result->operator[](reference_token);
        break;
      }

      case detail::value_t::array: {
        // create an entry in the array
        result =
            &result->operator[](array_index<BasicJsonType>(reference_token));
        break;
      }

      /*
      The following code is only reached if there exists a reference
      token _and_ the current value is primitive. In this case, we have
      an error situation, because primitive values may only occur as
      single value; that is, with an empty list of reference tokens.
      */
      case detail::value_t::string:
      case detail::value_t::boolean:
      case detail::value_t::number_integer:
      case detail::value_t::number_unsigned:
      case detail::value_t::number_float:
      case detail::value_t::binary:
      case detail::value_t::discarded:
      default:
        JSON_THROW(
            detail::type_error::create(313, "invalid value to unflatten", &j));
      }
    }

    return *result;
  }

  /*!
  @brief return a reference to the pointed to value

  @note This version does not throw if a value is not present, but tries to
        create nested values instead. For instance, calling this function
        with pointer `"/this/that"` on a null value is equivalent to calling
        `operator[]("this").operator[]("that")` on that value, effectively
        changing the null value to an object.

  @param[in] ptr  a JSON value

  @return reference to the JSON value pointed to by the JSON pointer

  @complexity Linear in the length of the JSON pointer.

  @throw parse_error.106   if an array index begins with '0'
  @throw parse_error.109   if an array index was not a number
  @throw out_of_range.404  if the JSON pointer can not be resolved
  */
  template <typename BasicJsonType>
  BasicJsonType &get_unchecked(BasicJsonType *ptr) const {
    for (const auto &reference_token : reference_tokens) {
      // convert null values to arrays or objects before continuing
      if (ptr->is_null()) {
        // check if reference token is a number
        const bool nums =
            std::all_of(reference_token.begin(), reference_token.end(),
                        [](const unsigned char x) { return std::isdigit(x); });

        // change value to array for numbers or "-" or to object otherwise
        *ptr = (nums || reference_token == "-") ? detail::value_t::array
                                                : detail::value_t::object;
      }

      switch (ptr->type()) {
      case detail::value_t::object: {
        // use unchecked object access
        ptr = &ptr->operator[](reference_token);
        break;
      }

      case detail::value_t::array: {
        if (reference_token == "-") {
          // explicitly treat "-" as index beyond the end
          ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
        } else {
          // convert array index to number; unchecked access
          ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
        }
        break;
      }

      case detail::value_t::null:
      case detail::value_t::string:
      case detail::value_t::boolean:
      case detail::value_t::number_integer:
      case detail::value_t::number_unsigned:
      case detail::value_t::number_float:
      case detail::value_t::binary:
      case detail::value_t::discarded:
      default:
        JSON_THROW(detail::out_of_range::create(
            404,
            detail::concat("unresolved reference token '", reference_token,
                           "'"),
            ptr));
      }
    }

    return *ptr;
  }

  /*!
  @throw parse_error.106   if an array index begins with '0'
  @throw parse_error.109   if an array index was not a number
  @throw out_of_range.402  if the array index '-' is used
  @throw out_of_range.404  if the JSON pointer can not be resolved
  */
  template <typename BasicJsonType>
  BasicJsonType &get_checked(BasicJsonType *ptr) const {
    for (const auto &reference_token : reference_tokens) {
      switch (ptr->type()) {
      case detail::value_t::object: {
        // note: at performs range check
        ptr = &ptr->at(reference_token);
        break;
      }

      case detail::value_t::array: {
        if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) {
          // "-" always fails the range check
          JSON_THROW(detail::out_of_range::create(
              402,
              detail::concat("array index '-' (",
                             std::to_string(ptr->m_data.m_value.array->size()),
                             ") is out of range"),
              ptr));
        }

        // note: at performs range check
        ptr = &ptr->at(array_index<BasicJsonType>(reference_token));
        break;
      }

      case detail::value_t::null:
      case detail::value_t::string:
      case detail::value_t::boolean:
      case detail::value_t::number_integer:
      case detail::value_t::number_unsigned:
      case detail::value_t::number_float:
      case detail::value_t::binary:
      case detail::value_t::discarded:
      default:
        JSON_THROW(detail::out_of_range::create(
            404,
            detail::concat("unresolved reference token '", reference_token,
                           "'"),
            ptr));
      }
    }

    return *ptr;
  }

  /*!
  @brief return a const reference to the pointed to value

  @param[in] ptr  a JSON value

  @return const reference to the JSON value pointed to by the JSON
  pointer

  @throw parse_error.106   if an array index begins with '0'
  @throw parse_error.109   if an array index was not a number
  @throw out_of_range.402  if the array index '-' is used
  @throw out_of_range.404  if the JSON pointer can not be resolved
  */
  template <typename BasicJsonType>
  const BasicJsonType &get_unchecked(const BasicJsonType *ptr) const {
    for (const auto &reference_token : reference_tokens) {
      switch (ptr->type()) {
      case detail::value_t::object: {
        // use unchecked object access
        ptr = &ptr->operator[](reference_token);
        break;
      }

      case detail::value_t::array: {
        if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) {
          // "-" cannot be used for const access
          JSON_THROW(detail::out_of_range::create(
              402,
              detail::concat("array index '-' (",
                             std::to_string(ptr->m_data.m_value.array->size()),
                             ") is out of range"),
              ptr));
        }

        // use unchecked array access
        ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
        break;
      }

      case detail::value_t::null:
      case detail::value_t::string:
      case detail::value_t::boolean:
      case detail::value_t::number_integer:
      case detail::value_t::number_unsigned:
      case detail::value_t::number_float:
      case detail::value_t::binary:
      case detail::value_t::discarded:
      default:
        JSON_THROW(detail::out_of_range::create(
            404,
            detail::concat("unresolved reference token '", reference_token,
                           "'"),
            ptr));
      }
    }

    return *ptr;
  }

  /*!
  @throw parse_error.106   if an array index begins with '0'
  @throw parse_error.109   if an array index was not a number
  @throw out_of_range.402  if the array index '-' is used
  @throw out_of_range.404  if the JSON pointer can not be resolved
  */
  template <typename BasicJsonType>
  const BasicJsonType &get_checked(const BasicJsonType *ptr) const {
    for (const auto &reference_token : reference_tokens) {
      switch (ptr->type()) {
      case detail::value_t::object: {
        // note: at performs range check
        ptr = &ptr->at(reference_token);
        break;
      }

      case detail::value_t::array: {
        if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) {
          // "-" always fails the range check
          JSON_THROW(detail::out_of_range::create(
              402,
              detail::concat("array index '-' (",
                             std::to_string(ptr->m_data.m_value.array->size()),
                             ") is out of range"),
              ptr));
        }

        // note: at performs range check
        ptr = &ptr->at(array_index<BasicJsonType>(reference_token));
        break;
      }

      case detail::value_t::null:
      case detail::value_t::string:
      case detail::value_t::boolean:
      case detail::value_t::number_integer:
      case detail::value_t::number_unsigned:
      case detail::value_t::number_float:
      case detail::value_t::binary:
      case detail::value_t::discarded:
      default:
        JSON_THROW(detail::out_of_range::create(
            404,
            detail::concat("unresolved reference token '", reference_token,
                           "'"),
            ptr));
      }
    }

    return *ptr;
  }

  /*!
  @throw parse_error.106   if an array index begins with '0'
  @throw parse_error.109   if an array index was not a number
  */
  template <typename BasicJsonType>
  bool contains(const BasicJsonType *ptr) const {
    for (const auto &reference_token : reference_tokens) {
      switch (ptr->type()) {
      case detail::value_t::object: {
        if (!ptr->contains(reference_token)) {
          // we did not find the key in the object
          return false;
        }

        ptr = &ptr->operator[](reference_token);
        break;
      }

      case detail::value_t::array: {
        if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) {
          // "-" always fails the range check
          return false;
        }
        if (JSON_HEDLEY_UNLIKELY(
                reference_token.size() == 1 &&
                !("0" <= reference_token && reference_token <= "9"))) {
          // invalid char
          return false;
        }
        if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) {
          if (JSON_HEDLEY_UNLIKELY(
                  !('1' <= reference_token[0] && reference_token[0] <= '9'))) {
            // first char should be between '1' and '9'
            return false;
          }
          for (std::size_t i = 1; i < reference_token.size(); i++) {
            if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] &&
                                       reference_token[i] <= '9'))) {
              // other char should be between '0' and '9'
              return false;
            }
          }
        }

        const auto idx = array_index<BasicJsonType>(reference_token);
        if (idx >= ptr->size()) {
          // index out of range
          return false;
        }

        ptr = &ptr->operator[](idx);
        break;
      }

      case detail::value_t::null:
      case detail::value_t::string:
      case detail::value_t::boolean:
      case detail::value_t::number_integer:
      case detail::value_t::number_unsigned:
      case detail::value_t::number_float:
      case detail::value_t::binary:
      case detail::value_t::discarded:
      default: {
        // we do not expect primitive values if there is still a
        // reference token to process
        return false;
      }
      }
    }

    // no reference token left means we found a primitive value
    return true;
  }

  /*!
  @brief split the string input to reference tokens

  @note This function is only called by the json_pointer constructor.
        All exceptions below are documented there.

  @throw parse_error.107  if the pointer is not empty or begins with '/'
  @throw parse_error.108  if character '~' is not followed by '0' or '1'
  */
  static std::vector<string_t> split(const string_t &reference_string) {
    std::vector<string_t> result;

    // special case: empty reference string -> no reference tokens
    if (reference_string.empty()) {
      return result;
    }

    // check if nonempty reference string begins with slash
    if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) {
      JSON_THROW(detail::parse_error::create(
          107, 1,
          detail::concat(
              "JSON pointer must be empty or begin with '/' - was: '",
              reference_string, "'"),
          nullptr));
    }

    // extract the reference tokens:
    // - slash: position of the last read slash (or end of string)
    // - start: position after the previous slash
    for (
        // search for the first slash after the first character
        std::size_t slash = reference_string.find_first_of('/', 1),
                    // set the beginning of the first reference token
        start = 1;
        // we can stop if start == 0 (if slash == string_t::npos)
        start != 0;
        // set the beginning of the next reference token
        // (will eventually be 0 if slash == string_t::npos)
        start = (slash == string_t::npos) ? 0 : slash + 1,
                    // find next slash
        slash = reference_string.find_first_of('/', start)) {
      // use the text between the beginning of the reference token
      // (start) and the last slash (slash).
      auto reference_token = reference_string.substr(start, slash - start);

      // check reference tokens are properly escaped
      for (std::size_t pos = reference_token.find_first_of('~');
           pos != string_t::npos;
           pos = reference_token.find_first_of('~', pos + 1)) {
        JSON_ASSERT(reference_token[pos] == '~');

        // ~ must be followed by 0 or 1
        if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
                                 (reference_token[pos + 1] != '0' &&
                                  reference_token[pos + 1] != '1'))) {
          JSON_THROW(detail::parse_error::create(
              108, 0, "escape character '~' must be followed with '0' or '1'",
              nullptr));
        }
      }

      // finally, store the reference token
      detail::unescape(reference_token);
      result.push_back(reference_token);
    }

    return result;
  }

 private:
  /*!
  @param[in] reference_string  the reference string to the current value
  @param[in] value             the value to consider
  @param[in,out] result        the result object to insert values to

  @note Empty objects or arrays are flattened to `null`.
  */
  template <typename BasicJsonType>
  static void flatten(const string_t &reference_string,
                      const BasicJsonType &value, BasicJsonType &result) {
    switch (value.type()) {
    case detail::value_t::array: {
      if (value.m_data.m_value.array->empty()) {
        // flatten empty array as null
        result[reference_string] = nullptr;
      } else {
        // iterate array and use index as reference string
        for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i) {
          flatten(detail::concat(reference_string, '/', std::to_string(i)),
                  value.m_data.m_value.array->operator[](i), result);
        }
      }
      break;
    }

    case detail::value_t::object: {
      if (value.m_data.m_value.object->empty()) {
        // flatten empty object as null
        result[reference_string] = nullptr;
      } else {
        // iterate object and use keys as reference string
        for (const auto &element : *value.m_data.m_value.object) {
          flatten(detail::concat(reference_string, '/',
                                 detail::escape(element.first)),
                  element.second, result);
        }
      }
      break;
    }

    case detail::value_t::null:
    case detail::value_t::string:
    case detail::value_t::boolean:
    case detail::value_t::number_integer:
    case detail::value_t::number_unsigned:
    case detail::value_t::number_float:
    case detail::value_t::binary:
    case detail::value_t::discarded:
    default: {
      // add primitive value with its reference string
      result[reference_string] = value;
      break;
    }
    }
  }

  /*!
  @param[in] value  flattened JSON

  @return unflattened JSON

  @throw parse_error.109 if array index is not a number
  @throw type_error.314  if value is not an object
  @throw type_error.315  if object values are not primitive
  @throw type_error.313  if value cannot be unflattened
  */
  template <typename BasicJsonType>
  static BasicJsonType unflatten(const BasicJsonType &value) {
    if (JSON_HEDLEY_UNLIKELY(!value.is_object())) {
      JSON_THROW(detail::type_error::create(
          314, "only objects can be unflattened", &value));
    }

    BasicJsonType result;

    // iterate the JSON object values
    for (const auto &element : *value.m_data.m_value.object) {
      if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) {
        JSON_THROW(detail::type_error::create(
            315, "values in object must be primitive", &element.second));
      }

      // assign value to reference pointed to by JSON pointer; Note that if
      // the JSON pointer is "" (i.e., points to the whole value), function
      // get_and_create returns a reference to result itself. An assignment
      // will then create a primitive value.
      json_pointer(element.first).get_and_create(result) = element.second;
    }

    return result;
  }

  // can't use conversion operator because of ambiguity
  json_pointer<string_t> convert() const & {
    json_pointer<string_t> result;
    result.reference_tokens = reference_tokens;
    return result;
  }

  json_pointer<string_t> convert() && {
    json_pointer<string_t> result;
    result.reference_tokens = std::move(reference_tokens);
    return result;
  }

 public:
#if JSON_HAS_THREE_WAY_COMPARISON
  /// @brief compares two JSON pointers for equality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
  template <typename RefStringTypeRhs>
  bool operator==(const json_pointer<RefStringTypeRhs> &rhs) const noexcept {
    return reference_tokens == rhs.reference_tokens;
  }

  /// @brief compares JSON pointer and string for equality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
  JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
  bool operator==(const string_t &rhs) const {
    return *this == json_pointer(rhs);
  }

  /// @brief 3-way compares two JSON pointers
  template <typename RefStringTypeRhs>
  std::strong_ordering operator<=>(
      const json_pointer<RefStringTypeRhs> &rhs) const noexcept  // *NOPAD*
  {
    return reference_tokens <=> rhs.reference_tokens;  // *NOPAD*
  }
#else
  /// @brief compares two JSON pointers for equality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
  template <typename RefStringTypeLhs, typename RefStringTypeRhs>
  // NOLINTNEXTLINE(readability-redundant-declaration)
  friend bool operator==(const json_pointer<RefStringTypeLhs> &lhs,
                         const json_pointer<RefStringTypeRhs> &rhs) noexcept;

  /// @brief compares JSON pointer and string for equality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
  template <typename RefStringTypeLhs, typename StringType>
  // NOLINTNEXTLINE(readability-redundant-declaration)
  friend bool operator==(const json_pointer<RefStringTypeLhs> &lhs,
                         const StringType &rhs);

  /// @brief compares string and JSON pointer for equality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
  template <typename RefStringTypeRhs, typename StringType>
  // NOLINTNEXTLINE(readability-redundant-declaration)
  friend bool operator==(const StringType &lhs,
                         const json_pointer<RefStringTypeRhs> &rhs);

  /// @brief compares two JSON pointers for inequality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
  template <typename RefStringTypeLhs, typename RefStringTypeRhs>
  // NOLINTNEXTLINE(readability-redundant-declaration)
  friend bool operator!=(const json_pointer<RefStringTypeLhs> &lhs,
                         const json_pointer<RefStringTypeRhs> &rhs) noexcept;

  /// @brief compares JSON pointer and string for inequality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
  template <typename RefStringTypeLhs, typename StringType>
  // NOLINTNEXTLINE(readability-redundant-declaration)
  friend bool operator!=(const json_pointer<RefStringTypeLhs> &lhs,
                         const StringType &rhs);

  /// @brief compares string and JSON pointer for inequality
  /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
  template <typename RefStringTypeRhs, typename StringType>
  // NOLINTNEXTLINE(readability-redundant-declaration)
  friend bool operator!=(const StringType &lhs,
                         const json_pointer<RefStringTypeRhs> &rhs);

  /// @brief compares two JSON pointer for less-than
  template <typename RefStringTypeLhs, typename RefStringTypeRhs>
  // NOLINTNEXTLINE(readability-redundant-declaration)
  friend bool operator<(const json_pointer<RefStringTypeLhs> &lhs,
                        const json_pointer<RefStringTypeRhs> &rhs) noexcept;
#endif

 private:
  /// the reference tokens
  std::vector<string_t> reference_tokens;
};

#if !JSON_HAS_THREE_WAY_COMPARISON
// functions cannot be defined inside class due to ODR violations
template <typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator==(const json_pointer<RefStringTypeLhs> &lhs,
                       const json_pointer<RefStringTypeRhs> &rhs) noexcept {
  return lhs.reference_tokens == rhs.reference_tokens;
}

template <
    typename RefStringTypeLhs,
    typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
inline bool operator==(const json_pointer<RefStringTypeLhs> &lhs,
                       const StringType &rhs) {
  return lhs == json_pointer<RefStringTypeLhs>(rhs);
}

template <
    typename RefStringTypeRhs,
    typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
inline bool operator==(const StringType &lhs,
                       const json_pointer<RefStringTypeRhs> &rhs) {
  return json_pointer<RefStringTypeRhs>(lhs) == rhs;
}

template <typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator!=(const json_pointer<RefStringTypeLhs> &lhs,
                       const json_pointer<RefStringTypeRhs> &rhs) noexcept {
  return !(lhs == rhs);
}

template <
    typename RefStringTypeLhs,
    typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
inline bool operator!=(const json_pointer<RefStringTypeLhs> &lhs,
                       const StringType &rhs) {
  return !(lhs == rhs);
}

template <
    typename RefStringTypeRhs,
    typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
inline bool operator!=(const StringType &lhs,
                       const json_pointer<RefStringTypeRhs> &rhs) {
  return !(lhs == rhs);
}

template <typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator<(const json_pointer<RefStringTypeLhs> &lhs,
                      const json_pointer<RefStringTypeRhs> &rhs) noexcept {
  return lhs.reference_tokens < rhs.reference_tokens;
}
#endif

NLOHMANN_JSON_NAMESPACE_END
